// Copyright Leonid Shlyapnikov 2010
// Distributed under the GNU Lesser General Public License http://www.gnu.org/licenses/lgpl.html
//    author: Leonid Shlyapnikov, created on: Jan 23, 2010

#ifndef FIXPARSER_CPP_SRC_MAIN_FIXPARSER_SIMPLEFIXMESSAGE_H_
#define FIXPARSER_CPP_SRC_MAIN_FIXPARSER_SIMPLEFIXMESSAGE_H_

#include <boost/utility.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/index_of.hpp>
#include <boost/foreach.hpp>
#include <boost/exception/all.hpp>
#include <exception>
#include <string>
#include <cstring>
#include <sstream>

#include "./MemoryArea.h"
#include "./Base.h"
#include "./Tag.h"
#include "./Field.h"
#include "./FieldList.h"
#include "./String.h"
#include "./ChecksumConverter.h"
#include "./log.h"

namespace fixparser {

template<class T>
class SimpleFixMessage: public Base {
public:
    friend class MemoryArea;

    typedef typename T::cached_tags_mpl_list cached_tags_mpl_list;

    static const int NUMBER_OF_CACHED_FIELDS = boost::mpl::size< cached_tags_mpl_list >::value;

    template<size_t TAG_NUM>
    bool isFieldSet() const {
        const size_t index = mpl_tag_index< TAG_NUM, T >::result;
        Field* field = this->cachedFields_[index];
        return (0 != field);
    }

    size_t numberOfFields() const {
        if (checksumField_.isEmpty()) {
            return this->fieldList_.size();
        } else {
            return this->fieldList_.size() + 1;
        }
    }

    template<size_t TAG_NUM>
    const String& value() const {
        const size_t index = mpl_tag_index< TAG_NUM, T >::result;
        Field* field = this->cachedFields_[index];
        if (0 == field) {
            std::stringstream error;
            error << "Field: " << TAG_NUM << " is not set";
            BOOST_THROW_EXCEPTION(std::invalid_argument(error.str()));
        } else {
            return field->value();
        }
    }

    /**
     * Throws std::invalid_argument if TAG_NUM field is not set.
     */
    template<size_t TAG_NUM>
    const Field* field() const {
        const size_t index = mpl_tag_index< TAG_NUM, T >::result;
        Field* field = this->cachedFields_[index];
        if (0 == field) {
            std::stringstream error;
            error << "Field: " << TAG_NUM << " is not set";
            BOOST_THROW_EXCEPTION(std::invalid_argument(error.str()));
        }
        return field;
    }

    template<size_t TAG_NUM, class STR>
    void setValue(const STR& newValue) {
        Field* field = getFieldFromCacheOrCreate<TAG_NUM>();
        field->setValue(newValue);
    }

    /**
     * Accepts std::string and fixparser::String parameters.
     */
    template<size_t TAG_NUM>
    void setValue(const char* newValue) {
        Field* field = getFieldFromCacheOrCreate<TAG_NUM>();
        field->setValue(newValue);
    }

    const String& checksum() const {
        return this->checksumField_.value();
    }

    void setChecksum(const String& checksum) {
        this->checksumField_.setValue(checksum);
    }

    void setChecksum() {
        const unsigned int checksum = this->calculateChecksum();
        const std::string checksumStr = ChecksumConverter::instance().checksumToString(checksum);
        this->checksumField_.setValue(checksumStr);
    }

    void addField(Field* field) {
        fieldList_.push_back(field);
    }

    /**
     * Accepts std::string and fixparser::String parameters.
     */
    template<class STR>
    void addField(const STR& tag, const STR& value) {
        Field* field = memoryArea_->create<Field>();
        field->setTag(tag);
        field->setValue(value);
        this->addField(field);
    }

    void addField(const char* tag, const char* value) {
        Field* field = memoryArea_->create<Field>();
        field->setTag(tag);
        field->setValue(value);
        this->addField(field);
    }

    template<class VISITOR>
    void acceptVisitor(VISITOR* visitor) {
        // don't forget about the required order of fields (check the spec):
        // 8=..|9=..|34=..(??)|35=..(??)| ... 10=..|

        BOOST_FOREACH(const Field& field, this->fieldList_) {
            field.acceptVisitor(visitor);
        }

        if (false == checksumField_.isEmpty()) {
            checksumField_.acceptVisitor(visitor);
        }
    }

    template<class VISITOR>
    void acceptVisitor(VISITOR* visitor) const {
        BOOST_FOREACH(const Field& field, this->fieldList_) {
            field.acceptVisitor(visitor);
        }

        if (false == checksumField_.isEmpty()) {
            checksumField_.acceptVisitor(visitor);
        }
    }

    unsigned int calculateChecksum() const {
        unsigned int result = 0;

        BOOST_FOREACH(const Field& field, this->fieldList_) {
            result += field.calculateChecksum();
        }

        return result;
    }

private:
    explicit SimpleFixMessage(MemoryArea* const ma) : memoryArea_(ma), fieldList_(ma), checksumField_(ma) {
        init();
    }

    virtual ~SimpleFixMessage() {}

    void init() {
        // set cachedFields to 0, don't want to use sizeof static array, if decide to switch to dynamic array, this will be broken
        std::memset(this->cachedFields_, 0, NUMBER_OF_CACHED_FIELDS * sizeof(cachedFields_[0]));
        this->checksumField_.setTag(TagConverter::tagIdToString(10));
    }

    /**
     * Gets field from the cache by tag number (field index) if field is not cached yet, creates it.
     */
    template<size_t TAG_NUM>
    Field* getFieldFromCacheOrCreate() {

        // convert field tag number into field cache index
        const size_t index = mpl_tag_index< TAG_NUM, T >::result;

        Field* field = this->cachedFields_[index];

        if (0 == field) {
            // allocate a new field
            field = memoryArea_->create<Field> ();
            // cache it
            this->cachedFields_[index] = field;
            // add the field to the list of all fields
            this->addField(field);
            // set field tag
            field->setTag(TagConverter::tagIdToString(TAG_NUM));
        }

        return field;
    }

    MemoryArea* const memoryArea_;

    // list of all fields
    FieldList fieldList_;

    // array of pointers to elements in fieldList_
    Field* cachedFields_[NUMBER_OF_CACHED_FIELDS];

    // a special field for checksum
    Field checksumField_;
};

}

/*
unsigned int checksum( char *buf, long bufLen )
{
    static char tmpBuf[ 4 ];
    long idx;
    unsigned int cks;
    for( idx = 0L, cks = 0; idx < bufLen; cks += (unsigned int)buf[ idx++ ] );
    sprintf( tmpBuf, “%03d”, (unsigned int)( cks % 256 ) );
    return( tmpBuf );
}
*/


#endif  // FIXPARSER_CPP_SRC_MAIN_FIXPARSER_SIMPLEFIXMESSAGE_H_
